package org.windowkit.toolkit.widget.table;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.Hashtable;
import java.util.List;

import javax.swing.JTable;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.TableColumnModelEvent;
import javax.swing.event.TableColumnModelListener;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;

import org.ddevil.data.Attribute;
import org.ddevil.data.BasicDisplayDescriptor;
import org.ddevil.data.Data;
import org.ddevil.data.DisplayDescriptor;
import org.ddevil.data.Filter;
import org.ddevil.data.ChangeTracker.Change;
import org.ddevil.data.set.BasicDataSet;
import org.ddevil.data.set.DataSet;
import org.ddevil.data.util.DataSetUtils;


/**
 * This table allows the developer to use {@link DataSetInterface} and
 * {@link Record} objects to more easily work with data.
 *
 * @author Eric Lundin
 */
@SuppressWarnings("serial")
public class RecordTablePane extends AbstractTablePane {

	Hashtable<String, TableCellEditor> columnEditors;

	public RecordTablePane() {
		this(new BasicDataSet(new String[] {}), new BasicDisplayDescriptor(
				new String[] { "FAKE" }, new String[] { "Fake" }), null);
	}

	public RecordTablePane(final DisplayDescriptor labels, final String identifier) {
		this(new BasicDataSet(new String[] {}), labels, identifier);
	}

	/**
	 * Construct a table and set the dataset to the given dataset. Usefull for
	 * defining column names and identifiers if the table will be displayed
	 * before data is gotten.
	 *
	 * @param dataSet
	 *            The dataset to populate this table with.
	 * @param identifier
	 *            The identifier for the table. Needed to retrieve preferences
	 *            after setting the new dataset.
	 */
	public RecordTablePane(final DataSet<Data> dataSet, final DisplayDescriptor labels,
			final String identifier) {
		super(new RecordTableModel(dataSet, labels, 0));

		setLabels(labels, identifier);
		setData(dataSet);

		theTable.getColumnModel().addColumnModelListener(
				new RecordTableColumnModelListener());
	}

	/**
	 * @return A casted reference to our model.
	 */
	protected RecordTableModel getModel() {
		/* Our model must be a RecordTableModel */
		return (RecordTableModel) model;
	}

	/**
	 * Set the labels for this table. Clears all table data and structure.
	 *
	 * @param labels
	 *            The labels to display.
	 * @param identifier
	 *            The identifier for this table.
	 */
	public void setLabels(final DisplayDescriptor labels) {
		setLabels(labels, null);
	}
	@Deprecated
	public void setLabels(final DisplayDescriptor labels, final String identifier) {
		// update the model
		getModel().setLabels(labels);

		// reload stuff that is based of the new labels
		columnSetChanged();
//
//		// reset the id, this will load prefs if they exist
//		setIdentifier(identifier);

	}

	/**
	 * Set the table's data to the given dataset.
	 *
	 * @param data
	 *            The data set to use.
	 */
	public void setData(final DataSet data) {
		// discard any editing(filter row)
		if (theTable.isEditing()) {
			theTable.getCellEditor().cancelCellEditing();
		}
		getModel().setData(data);
		// this is the only way i can figure out to clear any previous sorts
		theTable.getTableHeader().repaint();
	}


	/**
	 * Set the table's selection tot he first row where the attributes in the
	 * given attributes object are equal to the table row.
	 *
	 * @param atts
	 */
	public void setSelectionByData(final Data atts) {
		boolean equal;
		for (int i = 0; i < model.getDataRowCount(); i++) {
			equal = true;
			for (Attribute di : atts) {
				if (!DataSetUtils.safeEquals(model.getValueAt(i, getModel()
						.getModelIndex(di.getId())), di
						.getValue())) {
					equal = false;
					break;
				}
			}
			if (equal) {
				int index = i;
				if (model.isFilterable()) {
					index++;
				}
				theTable.getSelectionModel().clearSelection();
				theTable.getSelectionModel().addSelectionInterval(index, index);
			}
		}
	}

	/**
	 * Add a record.
	 *
	 * @param newRecord
	 *            The record to add.
	 */
	public void addRecord(final Data newRecord) {
		getModel().addRecord(newRecord);
	}

	/**
	 * Add a set of records.
	 *
	 * @param records
	 *            The record group to add.
	 */
	public void addRecords(final DataSet records) {
		getModel().addRecords(records);
	}

	/**
	 * Get the first record currently selected by the user.
	 *
	 * @return The record selected by the user.
	 */
	public Data getSelectedRecord() {
		Data selectedRecord = null;

		ArrayList<Integer> rows = getSelectedRows();
		if (rows.size() > 0) {
			selectedRecord = getModel().getRecord(rows.get(0));
		}
		return selectedRecord;
	}

	/**
	 * Get a {@link RecordGroup} of all selected records.
	 *
	 * @return A {@link RecordGroup} of all the records currently selected in
	 *         the table.
	 */
	public DataSet getSelectedRecords() {
		DataSet records = null;
		int[] selectedRows = getSelectedRowsArray();
		// TODO maybe this should be returning null if nothing
		// is selected. right now it returns a data set with
		// no records. getSelectedRecord() returns null

		records = getModel().getRecords(selectedRows);

		return records;
	}

	/**
	 * Sets a value which will be displayed at the beginning of the value for
	 * each entry in the columns identified by the given database identifiers.
	 *
	 * @param prefix
	 *            The characters to display at the beginning of each piece of
	 *            data.
	 * @param dbIdentifiers
	 *            The database identifiers to assign this prefix too.
	 */
	public void setPrefixForColumnValues(final String prefix, final String[] dbIdentifiers) {
		// TODO consider delegating entirely to model
		// int[] columns = new int[dbIdentifiers.length];
		// for (int i = 0; i < columns.length; i++) {
		// columns[i] = ((RecordTableModel) model)
		// .getModelIndex(dbIdentifiers[i]);
		// }
		// sorter.setPrefixForColumnValues(prefix, columns);
	}

	/**
	 * TODO consider removing this. #removeRow(int) should be fine Remove the
	 * record at the given index.
	 *
	 * @param index
	 *            The index to remove.
	 */
	public void removeRecord(final int index) {
		removeRow(index);
	}


	public void setColumnVisible(final String id, final boolean visible) {
		if (visible) {

			addVisibleColumn(getModel().getModelIndex(id));
		}
		else {
			removeVisibleColumn(getModel().getModelIndex(id));
		}
	}

	/**
	 * Set the {@link TableCellEditor} to use for the specified column. Columns
	 * are specified by their DB Id.
	 *
	 * @param dbid
	 *            The identifier for the column.
	 * @param editor
	 *            The editor to use.
	 */
	public void setColumnCellEditor(final String dbid, final TableCellEditor editor) {

		if (editor == null) {
			// remove
			if (columnEditors != null) {
				columnEditors.remove(dbid);
				getModel().setColumnEditable(dbid, false);
			}
		} else {
			// add
			if (columnEditors == null) {
				columnEditors = new Hashtable<String, TableCellEditor>();
			}
			// store it here incase we need to reset it
			columnEditors.put(dbid, editor);

			// update the model
			getModel().setColumnEditable(dbid, true);

			// update the column model
			int modIndex = getModel().getModelIndex(dbid);
			TableColumnModel tcm = theTable.getColumnModel();
			for (int i = 0; i < tcm.getColumnCount(); i++) {
				if (tcm.getColumn(i).getModelIndex() == modIndex) {
					tcm.getColumn(i).setCellEditor(editor);
					break;
				}
			}

		}
	}

	public void setColumnCellRenderer(final String id, final TableCellRenderer renderer) {
		// update the column model
		int modIndex = getModel().getModelIndex(id);
		TableColumnModel tcm = theTable.getColumnModel();
		for (int i = 0; i < tcm.getColumnCount(); i++) {
			if (tcm.getColumn(i).getModelIndex() == modIndex) {
				tcm.getColumn(i).setCellRenderer(renderer);
				break;
			}
		}
	}


	/**
	 * Set the renderer to use for a class.
	 * Delegate for {@link JTable#setDefaultRenderer(Class, TableCellRenderer)}.
	 * @param clazz
	 * @param renderer
	 */
	public void setDefaultRenderer(final Class<?> clazz, final TableCellRenderer renderer) {
		theTable.setDefaultRenderer(clazz, renderer);
	}


	/**
	 * Set the comparator to use on the given column.
	 *
	 * @param columnId
	 *            The name of the identifier for the column.
	 * @param comp
	 *            The comparator to use.
	 */
	public void setColumnComparator(final String columnId, final Comparator comp) {
		getModel().setComparator(columnId, comp);
	}

	/**
	 * Set the comparator to use on the given columns.
	 *
	 * @param dbNames
	 *            The database identifiers of the columns to apply this
	 *            comparator to.
	 * @param myComparator
	 *            The comparator to use on these columns.
	 */
	public void setColumnComparator(final String[] dbNames, final Comparator myComparator) {
		for (String current : dbNames) {
			setColumnComparator(current, myComparator);
		}
	}

	/**
	 * // TOOD implement method that takes FILTER
	 */
	@Deprecated
	public void setFilter(final String columnId, final String filter) {
		((RecordTableModel)model).setFilter(columnId, filter);
	}
	public void setFilter(final String columnId, final Filter filter) {

	}


	public List<Change> getChanges() {
		return getModel().getChanges();
	}

	public void resetChangeTracker() {
		getModel().resetChangeTracker();
	}

	public void trackChanges(final boolean track) {
		getModel().trackChanges(track);
	}

	protected class RecordTableColumnModelListener implements
			TableColumnModelListener {
		/*
		 * If our column model is rebuilt or changes and a column is added we
		 * need to check our custom editor/renderer lists to see if that column
		 * needs to have a cell editor or renderer set.
		 */
		public void columnAdded(final TableColumnModelEvent e) {
			if (columnEditors != null) {
				TableColumn col = theTable.getColumnModel().getColumn(
						e.getToIndex());
				String dbid = getModel().getDataBaseID(col.getModelIndex());
				if (columnEditors.containsKey(dbid)) {
					col.setCellEditor(columnEditors.get(dbid));
				}
			}
		}

		public void columnRemoved(final TableColumnModelEvent e) {
		}

		public void columnMoved(final TableColumnModelEvent e) {
		}

		public void columnMarginChanged(final ChangeEvent e) {
		}

		public void columnSelectionChanged(final ListSelectionEvent e) {
		}
	}

}
